package com.hj.springframework2.io;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

/**
 * @Author : hujing
 * @Date: 2025/6/12 16:39
 * @Description:资源解析器,根据定义的扫描包路径,按照mapper函数对资源文件进行处理
 */
public class ResourceResolver {
    final Logger logger = LoggerFactory.getLogger(getClass());

    //定义扫描的包 例:"com.hj.springframework2.scan"
    String basePackage;

    public ResourceResolver(String basePackage) {
        this.basePackage = basePackage;
    }

    /**
     * @param mapper
     * @param <R>
     * @return
     */
    public <R> List<R> scan(Function<Resource, R> mapper) {
        String basePackagePath = this.basePackage.replace(".", "/");

        try {
            ArrayList<R> collector = new ArrayList<>();
            scan0(basePackagePath, basePackagePath, collector, mapper);
            return collector;
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 扫描指定包下的所有资源文件,并对每个找到的资源文件应用一个映射函数
     * @param basePackagePath 基础包路径,例如 com/hj/springframework2/scan
     * @param path com/hj/springframework2/scan
     * @param collector
     * @param mapper
     * @param <R>
     * @throws IOException
     * @throws URISyntaxException
     */
    private <R> void scan0(String basePackagePath, String path, ArrayList<R> collector, Function<Resource, R> mapper) throws IOException, URISyntaxException {
        logger.info("scan path:{}", path);
        Enumeration<URL> en = getContextClassLoader().getResources(path);
        while (en.hasMoreElements()) {
            URL url = en.nextElement();
            URI uri = url.toURI();//URI是不包含协议,主机,端口
            String uriStr = removeTrailingSlash(uriToString(uri));//file:/F:/project/spring-parent/spring-framework2/target/test-classes/com/hj/springframework2/scan
            String uriBaseStr = uriStr.substring(0, uriStr.length() - basePackagePath.length());//file:/F:/project/spring-parent/spring-framework2/target/test-classes/
            if (uriBaseStr.startsWith("file:")) {
                uriBaseStr = uriBaseStr.substring(5);
            }
            if (uriBaseStr.startsWith("jar:")) {
                scanFile(true, uriBaseStr, jarUriToPath(basePackagePath, uri), collector, mapper);
            } else {
                scanFile(false, uriBaseStr, Paths.get(uri), collector, mapper);
            }
        }
    }

    /**
     * 
     * @param isJar 是否是 JAR 文件
     * @param base 扫描的基准路径 /F:/project/spring-parent/spring-framework2/target/test-classes
     * @param root 扫描的根路径
     * @param collector 结果集
     * @param mapper 函数式接口,用于对扫描的每个Resource对象进行处理
     * @param <R> 泛型声明,方法返回值类型
     * @throws IOException
     */
    <R> void scanFile(boolean isJar, String base, Path root, List<R> collector, Function<Resource, R> mapper) throws IOException {
        String baseDir = removeTrailingSlash(base);
        //Files::isRegularFile过滤非常规文件(例如目录,符号链接等),只保留常规文件
        Files.walk(root).filter(Files::isRegularFile).forEach(file -> {
            Resource res;
            if (isJar) {
                res = new Resource(baseDir, removeLeadingSlash(file.toString()));
            } else {
                String path = file.toString();
                String name = removeLeadingSlash(path.substring(baseDir.length()));
                res = new Resource("file:" + path, name);
            }
            logger.info("found resource:{}", res);
            R r = mapper.apply(res);
            if (r != null) {
                collector.add(r);
            }
        });
    }

    /**
     * 将一个 JAR 文件的 URI 转换为一个可以用来访问 JAR 文件内部内容的 Path 对象
     *
     * @param basePackagePath
     * @param jarUri
     * @return
     * @throws IOException
     */
    Path jarUriToPath(String basePackagePath, URI jarUri) throws IOException {
        //使用 FileSystems.newFileSystem 方法，基于给定的 JAR 文件 URI 创建一个新的文件系统视图
        //这个文件系统视图允许我们像访问普通文件系统一样访问 JAR 文件内的文件和目录
        return FileSystems.newFileSystem(jarUri, Map.of()).getPath(basePackagePath);
    }

    /**
     * 对URI对象中包含的特殊字符,空格或其他编码的内容进行解码
     *
     * @param uri
     * @return
     */
    String uriToString(URI uri) {
        return URLDecoder.decode(uri.toString(), StandardCharsets.UTF_8);
    }

    /**
     * 获取当前线程的类加载器
     *
     * @return
     */
    ClassLoader getContextClassLoader() {
        ClassLoader cl;
        cl = Thread.currentThread().getContextClassLoader();
        if (cl == null) {
            cl = getClass().getClassLoader();
        }
        return cl;
    }

    /**
     * 删除开头的斜杠
     *
     * @param s
     * @return
     */
    String removeLeadingSlash(String s) {
        if (s.startsWith("/") || s.startsWith("\\")) {
            s = s.substring(1);
        }
        return s;
    }

    /**
     * 删除拖尾斜杠
     *
     * @param s
     * @return
     */
    String removeTrailingSlash(String s) {
        if (s.endsWith("/") || s.endsWith("\\")) {
            s = s.substring(0, s.length() - 1);
        }
        return s;
    }
}
